package org.xnat.xnatfs.connection;

import java.io.IOException;

import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.log4j.Logger;
import org.xnat.xnatfs.exception.RequestException;

import com.bradmcevoy.http.Auth;

public class HttpConnection implements IConnection {
  private static final Logger logger = Logger.getLogger ( HttpConnection.class );

  // Warning: the max connections seems to a limiting factor, if we run into
  // issues of being unable to download files, this is a likely culprit.
  private static final int MAX_CONNECTIONS = 100000;

  private String mURL;
  private HttpParams params;
  private SchemeRegistry schemeRegistry;
  private ClientConnectionManager cm;

  public HttpConnection () {
    params = new BasicHttpParams ();
    ConnManagerParams.setMaxTotalConnections ( params, MAX_CONNECTIONS );
    ConnManagerParams.setMaxConnectionsPerRoute ( params, new ConnPerRouteBean ( MAX_CONNECTIONS ) );
    HttpProtocolParams.setVersion ( params, HttpVersion.HTTP_1_1 );

    // Create and initialize scheme registry
    schemeRegistry = new SchemeRegistry ();
    schemeRegistry.register ( new Scheme ( "http", PlainSocketFactory.getSocketFactory (), 80 ) );
    schemeRegistry.register ( new Scheme ( "https", SSLSocketFactory.getSocketFactory (), 443 ) );

    // Create an HttpClient with the ThreadSafeClientConnManager.
    // This connection manager must be used if more than one thread will
    // be using the HttpClient.
    cm = new ThreadSafeClientConnManager ( params, schemeRegistry );
  }

  @Override
  public HttpResponse doGet ( String url, Auth auth ) throws RequestException {
    return execute ( new HttpGet ( formatURL ( url ) ), auth );
  }

  @Override
  public HttpResponse doHead ( String url, Auth auth ) throws RequestException {
    return execute ( new HttpHead ( formatURL ( url ) ), auth );
  }

  // @Override
  public HttpResponse doPost ( HttpPost post, Auth auth ) throws RequestException {
    return execute ( post, auth );
  }

  private HttpResponse execute ( HttpUriRequest request, Auth auth ) throws RequestException {
    // Generate BASIC scheme object and stick it to the local
    // execution context
    BasicHttpContext context = new BasicHttpContext ();
    BasicScheme basicAuth = new BasicScheme ();
    context.setAttribute ( "preemptive-auth", basicAuth );

    HttpResponse response;
    try {
      logger.debug ( "Sent HTTP " + request.getMethod () + " to " + request.getURI ().toString () );
      response = getClient ( auth ).execute ( request, context );
      logger.debug ( "Received response from HTTP " + request.getMethod () + " to " + request.getURI ().toString () );
    } catch ( ClientProtocolException e ) {
      throw new RequestException ( e );
    } catch ( IOException e ) {
      throw new RequestException ( e );
    }
    return response;
  }

  public String formatURL ( String path ) {
    if ( mURL == null ) {
      // URL wasn't configured, set it assuming that xnatfs is deployed within
      // the XNAT app
      mURL = ( new XnatContextProvider () ).getRestURL ();
    }

    return mURL + path;
  }

  public HttpClient getClient ( Auth credentials ) {
    DefaultHttpClient mClient;
    mClient = new DefaultHttpClient ( cm, params );
    logger.debug ( "Credentials are " + credentials.getUser () + "/********" );
    mClient.getCredentialsProvider ()
        .setCredentials ( new AuthScope ( AuthScope.ANY_HOST, AuthScope.ANY_PORT ), new UsernamePasswordCredentials ( credentials.getUser (), credentials.getPassword () ) );
    // Add as the first request interceptor
    mClient.addRequestInterceptor ( new PreemptiveAuthorization (), 0 );
    return mClient;
  }

  public String getURL () {
    return mURL;
  }

  public void setURL ( String url ) {
    mURL = url;
    if ( !mURL.endsWith ( "/REST" ) ) {
      logger
          .warn ( "Configured url '"
              + mURL
              + "' does not end with '/REST'.  '/REST' is the common path name for the XNAT REST api, and xnatfs may not properly work without this path.  Proceeding anyway, but be aware that I warned you!" );

    }
  }

}
